#include "RT2.h"
#include "KdNode.h"
#include "Triangle.h"
#include "KdTreeMemoryManager.h"
#include "TriangleListMemoryManager.h"
#include "AABB.h"
#include "OBJLoader.h"
#include "Ray.h"
#include "Intersections.h"
#include "Matrix4x4.h"
#include "Matrix3x3.h"
#include "RayTracer.h"
#include "KdTraversalStack.h"
#include "LightSource.h"
#include "ShadowRay.h"
#include "Vertex.h"
#include "Material.h"
#include "MinMaxBin.h"
#include "KdBuildQueue.h"
#include <sys/time.h>
#include <allegro.h>

KdTreeMemoryManager kdtmm;
TriangleListMemoryManager tlmm;

#ifndef USERECURSIVETRAVERSAL
KdTraversalStack stack;
#endif

#ifndef USERECURSIVEBUILD
KdBuildQueue queue;
#endif

BITMAP* buffer;
MinMaxBin bin;
Ray* rays[RT_SCREENW * SUPERSAMPLING][RT_SCREENH * SUPERSAMPLING];
SPoint viewPoint(.00001, .00001, 400.);
Vector viewVector(.0, .0, -1.0);
const float nearPlane = 10.;
#ifdef DEBUG
int debugRayX = 180;
int debugRayY = 120;
#endif
Matrix4x4 pointTransformations;
Matrix3x3 vectorTransformations;

int main()
{
//	std::cout << Vector(-1, 1, 0).getGamma() / AL_PI << std::endl;
//	
//	exit(-1);
	
	
	allegro_init();
	set_color_depth(32);
	int
			res =
					set_gfx_mode(GFX_AUTODETECT_WINDOWED, RT_SCREENW, RT_SCREENH, 0, 0);
	if (res != 0)
	{
		allegro_message(allegro_error);
		exit(-1);
	}

	install_timer();
	install_keyboard();
	install_mouse();

	vector<Triangle*> triangles;
	vector<Vertex*> vertices;
	vector<Material*> materials;
	
#ifdef USETEXTURE
	BITMAP* texture = load_bitmap("../textures/ripple.bmp", 0);
#endif

	OBJLoader::load("../obj/donut6144.obj", vertices, triangles, materials);

	LightSource light(SPoint(-3., 6.0, 2.), makecol(255, 255, 255), makecol(
			255, 255, 255));

	buffer = create_bitmap(SCREEN_W, SCREEN_H);

	for (int i = 0; i < RT_SCREENW * SUPERSAMPLING; i++)
		for (int j = 0; j < RT_SCREENH * SUPERSAMPLING; j++)
		{
			SPoint np(((float)i/(float)SUPERSAMPLING - RT_SCREENW / 2.)
					/ (RT_SCREENW / 20.), (RT_SCREENH / 2. - (float)j
					/(float)SUPERSAMPLING) / (RT_SCREENW / 20.), nearPlane);
			Vector v = np - viewPoint;
			rays[i][j] = new Ray(v, viewPoint);
		}

	// time
	timeval usec;
	gettimeofday(&usec, NULL);
	const int startTime = usec.tv_sec * 1000000 + usec.tv_usec;
	unsigned int frames = 0;
	unsigned long raysShot = 0;

	while (!key[KEY_ESC])
	{

		pointTransformations = Matrix4x4();
		vectorTransformations = Matrix3x3();
		if (key[KEY_LEFT])
		{
			pointTransformations.rotateY(-.1);
			vectorTransformations.rotateY(-.1);
		}
		if (key[KEY_RIGHT])
		{
			pointTransformations.rotateY(.1);
			vectorTransformations.rotateY(.1);
		}
		if (key[KEY_DOWN])
		{
			pointTransformations.rotateX(.1);
			vectorTransformations.rotateX(.1);
		}
		if (key[KEY_UP])
		{
			pointTransformations.rotateX(-.1);
			vectorTransformations.rotateX(-.1);
		}

#ifdef DEBUG
		if (key[KEY_W])
			debugRayY--;
		if (key[KEY_S])
			debugRayY++;
		if (key[KEY_A])
			debugRayX--;
		if (key[KEY_D])
			debugRayX++;
#endif

		// precalculate the vertex-values
		for (vector<Vertex*>::iterator it = vertices.begin(); it
				!= vertices.end(); it++)
			(*it)->precalculate(pointTransformations,
					vectorTransformations);

		// precalculate the triangle-values
		for (vector<Triangle*>::iterator it = triangles.begin(); it
				!= triangles.end(); it++)
			(*it)->precalculate(viewPoint);

		// determine the size of the aabb and create it
		SPoint min, max;
		for (vector<Triangle*>::const_iterator it = triangles.begin(); it
				!= triangles.end(); it++)
		{
			if (min.x == SPoint::UNKNOWN.x || (*it)->getMin(X) < min.x)
				min.x = (*it)->getMin(X);
			if (min.y == SPoint::UNKNOWN.y || (*it)->getMin(Y) < min.y)
				min.y = (*it)->getMin(Y);
			if (min.z == SPoint::UNKNOWN.z || (*it)->getMin(Z) < min.z)
				min.z = (*it)->getMin(Z);
			if (max.x == SPoint::UNKNOWN.x || (*it)->getMax(X) > max.x)
				max.x = (*it)->getMax(X);
			if (max.y == SPoint::UNKNOWN.y || (*it)->getMax(Y) > max.y)
				max.y = (*it)->getMax(Y);
			if (max.z == SPoint::UNKNOWN.z || (*it)->getMax(Z) > max.z)
				max.z = (*it)->getMax(Z);
		}
		AABB aabb(min, max);

		// create kd-tree
		KdNode* root =
#ifdef USERECURSIVEBUILD
				recBuildKdTree(triangles, aabb);
#else
		itBuildKdTree(triangles, aabb);
#endif

		int x, y;
		float r, g, b;
		for (int i = 0; i < RT_SCREENW; i++)
			for (int j = 0; j < RT_SCREENH; j++)
			{
				r = g = b = 0.;
				for (int k = 0; k < SUPERSAMPLING; k++)
					for (int l = 0; l < SUPERSAMPLING; l++)
					{

						x = i * SUPERSAMPLING + k;
						y = j * SUPERSAMPLING + l;

						Ray* ray = rays[x][y];

#ifdef USERECURSIVETRAVERSAL
						recTraverse(root, *ray, aabb);
#else
						itTraverse(root, *ray, aabb);
#endif

						const Triangle* triangle = ray->getHitTriangle();

						raysShot++;

#ifdef DEBUG
						if (x == debugRayX && y == debugRayY)
						{
							if (triangle)
								std::cout << "Ray hit triangle: " << triangle
										<< " after " << ray->getHitDistance()
										<< " and normal "
										<< triangle->getNormal(ray->getHitPoint())
										<< std::endl;
							//								std::cout << "Ray hit triangle: " << triangle << " vertex-normals: " << triangle->getVertex(0)->getNormal() << " " << triangle->getVertex(1)->getNormal() << " " << triangle->getVertex(2)->getNormal() << std::endl;
							else
								std::cout << "Missed!" << std::endl;
						}
#endif

						if (triangle)
						{
#ifdef USETEXTURE
							r = g = b = .5;
#else
							// ambient amount
							r
									+= (float)(getr(triangle->material->ambientReflection)
											* getr(light.ambientIntensity))
											* 0.00001537870049980777f;
							g
									+= (float)(getg(triangle->material->ambientReflection)
											* getg(light.ambientIntensity))
											* 0.00001537870049980777f;
							b
									+= (float)(getb(triangle->material->ambientReflection)
											* getb(light.ambientIntensity))
											* 0.00001537870049980777f;
#endif

							ShadowRay sr(ray->getHitPoint(), light.pos);
							Vector n = triangle->getNormal(ray->getHitPoint());

							float d = Vector::dotProduct(n, sr.getVector());

#ifdef DEBUG
							if (x == debugRayX && y == debugRayY)
							{
								std::cout << "Normal: " << d << std::endl;
							}
#endif

							if (d > .0 &&
#ifdef USERECURSIVETRAVERSAL
									recTraverseShadow(root, sr, aabb)
#else
									itTraverseShadow(root, sr, aabb)
#endif
							)
							{

#ifdef USETEXTURE
const float u = n.getSigma() / AL_PI * .99;
const float v = n.getGamma() / AL_PI * .99;
//std::cout << u << " " << v << std::endl;
const int c = _getpixel32(texture, u * texture->w, v * texture->h);
r = (float)getr(c) / 255.;
g = (float)getg(c) / 255.;
b = (float)getb(c) / 255.;
#else
								// diffuse amount
								r
										+= (float)(getr(triangle->material->diffuseReflection)
												* getr(light.diffuseIntensity))
												* d * 0.00001537870049980777f;
								g
										+= (float)(getg(triangle->material->diffuseReflection)
												* getg(light.diffuseIntensity))
												* d * 0.00001537870049980777f;
								b
										+= (float)(getb(triangle->material->diffuseReflection)
												* getb(light.diffuseIntensity))
												* d * 0.00001537870049980777f;
#endif

#ifdef USETEXTURE
#else
								// specular amount
								Vector ref = ~(ray->getVector() - n
										* Vector::dotProduct(ray->getVector(),
												n) * 2);
								float beta = pow(sMax(.0f, Vector::dotProduct(
										ref, -ray->getVector())),
										triangle->material->alpha);
								r
										+= getr(triangle->material->specularReflection)
												* getr(light.specularIntensity)
												* beta * 0.00001537870049980777;
								g
										+= getg(triangle->material->specularReflection)
												* getg(light.specularIntensity)
												* beta * 0.00001537870049980777;
								b
										+= getb(triangle->material->specularReflection)
												* getb(light.specularIntensity)
												* beta * 0.00001537870049980777;
#endif

								//								std::cout << r / 256 << " " << g / 256 << " " << b / 256 << std::endl;
							}

							raysShot++;
						}
						//						}
					}
				r /= SUPERSAMPLING * SUPERSAMPLING;
				g /= SUPERSAMPLING * SUPERSAMPLING;
				b /= SUPERSAMPLING * SUPERSAMPLING;
				_putpixel32(buffer, x / SUPERSAMPLING, y / SUPERSAMPLING,
						makecol((int)(r * 127.5), (int)(g * 127.5), (int)(b
								* 127.5)));
			}

#ifdef DEBUG
		rect(buffer, debugRayX - 1, debugRayY - 1, debugRayX + 1,
				debugRayY + 1, makecol(255, 255, 255));

		line(buffer, RT_SCREENW, 0, RT_SCREENW, RT_SCREENH * 2, makecol(255,
				255, 0));
		line(buffer, 0, RT_SCREENH, RT_SCREENW * 2, RT_SCREENH, makecol(255,
				255, 0));

		set_clip_rect(buffer, RT_SCREENW, 0, RT_SCREENW * 2, RT_SCREENH);
		textout_ex(buffer, font, "YZ", 500, 100, makecol(255, 255, 255), -1);
		drawKdTree(root, X, 500, 100, aabb);
		set_clip_rect(buffer, 0, RT_SCREENH, RT_SCREENW, RT_SCREENH * 2);
		textout_ex(buffer, font, "XZ", 100, 350, makecol(255, 255, 255), -1);
		drawKdTree(root, Y, 100, 350, aabb);
		set_clip_rect(buffer, RT_SCREENW, RT_SCREENH, RT_SCREENW * 2,
				RT_SCREENH * 2);
		textout_ex(buffer, font, "XY", 500, 350, makecol(255, 255, 255), -1);
		drawKdTree(root, Z, 500, 350, aabb);
		set_clip_rect(buffer, 0, 0, RT_SCREENW * 2, RT_SCREENH * 2);

#endif

		//		printKdTree(root);

		// clear kd-tree
		tlmm.clear();
		kdtmm.clear();

		// draw
		draw_sprite(screen, buffer, 0, 0);
		clear_to_color(buffer, makecol(0, 0, 0));
		//				rest(1);
		frames++;
	}

	// stop time
	gettimeofday(&usec, NULL);
	const int endTime = usec.tv_sec * 1000000 + usec.tv_usec;
	const int time = endTime - startTime;
	const double fps = (double)frames * 1000000 / time;
	std::cout << "FPS: " << fps << std::endl;
	std::cout << "Rays shot: " << raysShot << std::endl;
	const double rps = (double)raysShot * 1000000 / time;
	std::cout << "Rays/seconds: " << rps << std::endl;

	for (int i = 0; i < RT_SCREENW; i++)
		for (int j = 0; j < RT_SCREENH; j++)
			delete rays[i][j];

	for (vector<Triangle*>::iterator it = triangles.begin(); it
			!= triangles.end(); it++)
		delete *it;
	for (vector<Vertex*>::iterator it = vertices.begin(); it != vertices.end(); it++)
		delete *it;
	for (vector<Material*>::iterator it = materials.begin(); it
			!= materials.end(); it++)
		delete *it;
	
#ifdef USETEXTURE
	destroy_bitmap(texture);
#endif

	destroy_bitmap(buffer);
}
END_OF_MAIN()
